package data

import (
	"backend/config"
	"backend/internal/service"
	"backend/pkg/utils"
	"context"
	"fmt"
	"io"
	"log"
	"path/filepath"
	"strconv"
	"time"

	"github.com/go-redis/redis/extra/redisotel"
	"github.com/go-redis/redis/v8"
	"github.com/google/wire"
	"go.uber.org/zap"
	"gopkg.in/natefinch/lumberjack.v2"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"gorm.io/gorm/schema"
)

var ProviderSet = wire.NewSet(NewData, NewDB, NewTransaction, NewJwtRepo, NewUserRepo, NewRedis, NewMediaRepo)

type Data struct {
	db  *gorm.DB
	rdb *redis.Client
}

func NewData(logger *zap.Logger, db *gorm.DB, rdb *redis.Client) (*Data, func(), error) {
	cleanup := func() {
		logger.Info("closing the data resources")
	}

	return &Data{db: db, rdb: rdb}, cleanup, nil
}

func NewDB(conf *config.Configuration, gLog *zap.Logger) *gorm.DB {
	if conf.Db.Driver != "mysql" {
		gLog.Error(conf.Db.Driver + " driver is not supported")
		return nil
	}
	var writer io.Writer
	var logMode logger.LogLevel
	logFileDir := conf.Log.RootDir
	if !filepath.IsAbs(logFileDir) {
		logFileDir = filepath.Join(utils.RootPath(), logFileDir)
	}
	writer = &lumberjack.Logger{
		Filename: filepath.Join(logFileDir, conf.Db.LogFilename),
		MaxSize:  conf.Log.MaxSize,
		MaxAge:   conf.Log.MaxAge,
		Compress: conf.Log.Compress,
	}
	switch conf.Db.LogMode {
	case "silent":
		logMode = logger.Silent
	case "error":
		logMode = logger.Error
	case "warn":
		logMode = logger.Warn
	case "info":
		logMode = logger.Info
	default:
		logMode = logger.Info
	}
	newLogger := logger.New(log.New(writer, "\r\n", log.LstdFlags), logger.Config{
		SlowThreshold:             time.Second,
		Colorful:                  false,
		IgnoreRecordNotFoundError: false,
		LogLevel:                  logMode,
	})
	dsn := fmt.Sprintf(
		"%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local",
		conf.Db.UserName,
		conf.Db.Password,
		conf.Db.Host,
		strconv.Itoa(conf.Db.Port),
		conf.Db.Database,
		conf.Db.Charset,
	)
	// 连接mysql
	if db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			TablePrefix: "",
		},
		DisableForeignKeyConstraintWhenMigrating: true,      // 禁用自动创建外键约束
		Logger:                                   newLogger, // 使用自定义 Logger
	}); err != nil {
		gLog.Error("connect mysql failed", zap.Error(err))
		// 不panic，返回nil，让上层处理
		return nil
	} else {
		gLog.Info("connect mysql success")
		sqlDB, _ := db.DB()
		sqlDB.SetMaxIdleConns(conf.Db.MaxIdleConns)
		sqlDB.SetMaxOpenConns(conf.Db.MaxOpenConns)
		return db
	}
}

type contextTxKey struct{}

func (d *Data) ExecTx(ctx context.Context, fn func(ctx context.Context) error) error {
	return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		ctx = context.WithValue(ctx, contextTxKey{}, tx)
		return fn(ctx)
	})
}

func (d *Data) DB(ctx context.Context) *gorm.DB {
	tx, ok := ctx.Value(contextTxKey{}).(*gorm.DB)
	if ok {
		return tx
	}
	return d.db
}
func NewRedis(c *config.Configuration, gLog *zap.Logger) *redis.Client {
	client := redis.NewClient(&redis.Options{
		Addr:     c.Redis.Host + ":" + c.Redis.Port,
		Password: c.Redis.Password, // no password set
		DB:       c.Redis.DB,       // use default DB
	})

	client.AddHook(redisotel.TracingHook{})
	if err := client.Ping(context.Background()).Err(); err != nil {
		gLog.Error("redis connect failed, err:", zap.Any("err", err))
		// 不panic，返回nil，让上层处理
		return nil
	}

	return client
}

// NewTransaction .
func NewTransaction(d *Data) service.Transaction {
	return d
}
